/**
 * @(#)HibernateService.java Mar 5, 2007
 *
 * Copyright (c) 2005-2010 Psiqueware S.A. de C.V. All Rights Reserved.
 * This software is the proprietary information Psiqueware S.A. de C.V.
 * Use is subject to license terms.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * <br/>Usuario : AngelElectrico
 * <br/>Creaci&oacute;n: Mar 5, 2007 - 11:43:40 AM
 * <br/>
 * <br/>
 * <br/><t5>Cambios :</t5>
 * <br/><b>Fecha | Autor  |  Cambios</b>
 * <ul>
 * <li>Mar 5, 2007 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Creaci&oacute;n del Objeto.</li>
 *
 * </ul>
 */

package com.psiqueware.alheli.dao.hibernate;

import com.psiqueware.alheli.basico.BaseObject;
import com.psiqueware.alheli.dao.*;
import com.psiqueware.alheli.exception.NullArgumentException;
import com.psiqueware.alheli.exception.NullPostConditionException;
import com.psiqueware.alheli.logging.Logger;
import com.psiqueware.alheli.logging.LoggerFactory;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Indirecci&oacute;n para persistir clases atravez de Hibernate.
 * <br/>Paquete Original: com.psiqueware.basico.prueba.modelo.dao.hibernate
 * <br/>Nombre  Original: HbaService
 * <br/>Oprima para ver : <a href="HibernateService.java.html"><i>C&oacute;digo Fuente</i> </a>
 *
 * @author <a href="mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a>
 * @version Revision: 1.1 Mar 5, 2007 11:43:40 AM
 * @noinspection FeatureEnvy, UnusedDeclaration
 */
public final class HibernateService<H extends BaseObject>
{

    /**
     * Logger de clase para HibernateService
     */
    private final Logger m_log;


    /**
     * Constructor de HibernateService.
     *
     * @param claseQueUtilizaElServicio Clase que hace uso del {@link HibernateService}.
     */
    public HibernateService(Class claseQueUtilizaElServicio)
    {
        m_log = LoggerFactory.getLogger(claseQueUtilizaElServicio);
    }

    /**
     * Guarda un {@link H} en persistencia.
     * Este {@link H} no debe existir en persistencia o una
     * AlreadyPersistedObjectException debe brotar.
     *
     * @param o                   UsuarioPruebaK a persistir.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *
     * @return UsuarioPruebaK despu&eacute;s de persistirse.
     * Una com.psiqueware.basico.dao.AlreadyPersistedObjectException surgir&aacute;
     * en caso de que este {@link H} ya existiera en persistencia.
     *
     * @throws com.psiqueware.alheli.dao.DAOException en caso de un error.
     */
    public H save(H o, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {

        try
        {
            if (m_log.isDebugEnabled()) m_log.debug("<guardando>" + enlaceTransaccional + " - " + o + "</guardando>");
            if (null != o.getId())
            {
                final String error = AlreadyPersistedObjectException.STANDARD_MSG;
                m_log.error(error);
                throw new AlreadyPersistedObjectException(error, o);
            }
            o.setAlta(new Date());
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            s.save(o);
            return o;
        }
        catch (Exception e)
        {
            final String error = "<error><msg>" + enlaceTransaccional + " - No se pudo persistir el BaseObject.</msg><parametros>" + o + "</parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Actualiza un {@link H} en persistencia
     *
     * @param o                   HK a actualizar
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *
     * @return {@link H} actualizado
     * Una com.psiqueware.basico.dao.NotAPersistedObjectException surgir&aacute;
     * en caso de que este {@link H} no existiera en persistencia.
     *
     * @throws com.psiqueware.alheli.dao.DAOException en caso de un error.
     */
    public H update(H o, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (m_log.isDebugEnabled())
                m_log.debug("<actualizando>" + enlaceTransaccional + " - " + o + "</actualizando>");
            if (null == o.getId())
            {
                String error = enlaceTransaccional + " - " + NotAPersistedObjectException.STANDARD_MSG.replace("{0}", o.getClass().getName());
                m_log.error(error);
                throw new NotAPersistedObjectException(error, o);
            }
            if (o.getId() < 0)
            {
                String error = IdentificadorInvalidoException.STANDARD_MSG + " Id=" + o.getId();
                m_log.error(error);
                throw new IdentificadorInvalidoException(error, o);
            }
            o.setActualizacion(new Date());
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            s.update(o);
            return o;
        }
        catch (Exception e)
        {
            final String error = "<error><msg>No se pudo actualizar el BaseObject.</msg><parametros>" + o + "</parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Devuelve un {@link H} a partir de su id.
     *
     * @param clazz               buscada.
     * @param id                  del {@link H} que se busca
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *
     * @return {@link H} en caso de encontrarlo, <code>null</code> en otro caso.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public H findPorId(Class clazz, Long id, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (null == id)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " - El argumento 'id' no puede ser NULL.</error>");
            if (null == clazz)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " - El argumento 'clazz' no puede ser NULL.</error>");
            if (m_log.isDebugEnabled())
                m_log.debug("<buscando>" + enlaceTransaccional + "-" + clazz.getName() + ":" + id + "</buscando>");
            Session session = (Session) enlaceTransaccional.continuaTransaccion();
            if (null == session)
                throw new NullPostConditionException("<error>" + enlaceTransaccional + " - A fallado una postcondici&oacute;n. El argumento de salida 'session' no puede ser NULL.</error>");
            final Object o = session.get(clazz, id);
            Hibernate.initialize(o);
            //if ( m_log.isDebugEnabled () ) m_log.debug ( o );//<-Forzamos la inicializacion.
            return (H) o;
        }
        catch (HibernateException e)
        {
            final String error = "<error><msg>No se pudo cargar el BaseObject.</msg><parametros>" + id + "</parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Elimina un {@link H} del sistema de persistencia.
     *
     * @param o                   HK que se pretende elimninar.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *                            Una com.psiqueware.basico.dao.NotAPersistedObjectException surgir&aacute;
     *                            si dicho objeto no exist&iacute;a en persistencia.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si no se puede comunicar a la base de datos.
     * @noinspection CastToConcreteClass
     */
    public void delete(final H o, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (null == o)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " - el objeto a persistir no puede ser NULL.</error>");
            if (m_log.isDebugEnabled()) m_log.debug("<eliminando>" + enlaceTransaccional + " - " + o + "</eliminando>");
            if (null == o.getId())
            {
                String error = enlaceTransaccional + " - " + NotAPersistedObjectException.STANDARD_MSG.replace("{0}", o.getClass().getName());
                m_log.error(error);
                throw new NotAPersistedObjectException(error, o);
            }
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            s.delete(o);
            //o.setId(null);<-Esto provoca una excepci&oacute;n en Hibernate.
        }
        catch (Exception e)
        {
            final String error = "<error><msg>No se pudo actualizar el BaseObject.</msg><parametros>" + o + "</parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Devuelve un listado de los {@link H} que se encuentran en persistencia.
     *
     * @param strQuery            sentencia de HQL necesaria para saber que traer de la base de datos.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *
     * @return List de {@link H} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public int updateQuery(final String strQuery, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        return updateQuery(strQuery, Collections.EMPTY_MAP, enlaceTransaccional);
    }

    /**
     * Devuelve un listado de los {@link H} que se encuentran en persistencia.
     *
     * @param strQuery            sentencia de HQL necesaria para saber que traer de la base de datos.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     * @param params              par&aacute;metros para alimentar el query.
     *
     * @return List de {@link H} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public int updateQuery(final String strQuery, final Map<String, String> params, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (null == strQuery)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " -El argumento 'strQuery' no puede ser NULL.</error>");
            if (m_log.isDebugEnabled())
                m_log.debug("<actualizando>" + enlaceTransaccional + " - " + strQuery + "</actualizando>");
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            Query query = s.createQuery(strQuery);
            for (Map.Entry<String, String> param : params.entrySet())
            {
                query.setParameter(param.getKey(), param.getValue());
            }
            return query.executeUpdate();
        }
        catch (HibernateException e)
        {
            final String error = "<error><msg>" + enlaceTransaccional + " - No se pudier&oacute;n actualizar los BaseObject seg&uacute;n el Query</msg><parametros><query>" + strQuery + "</query><valores>" + params + "</valores></parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Devuelve un listado de los {@link H} que se encuentran en persistencia.
     *
     * @param strQuery            sentencia de HQL necesaria para saber que traer de la base de datos.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *
     * @return List de {@link H} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public List<H> queryList(final String strQuery, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        return queryList(strQuery, Collections.EMPTY_MAP, enlaceTransaccional);
    }

    /**
     * Devuelve un listado de los {@link H} que se encuentran en persistencia.
     *
     * @param strQuery            sentencia de HQL necesaria para saber que traer de la base de datos.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     * @param params              par&aacute;metros para alimentar el query.
     *
     * @return List de {@link H} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public List<H> queryList(final String strQuery, final Map<String, String> params, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (null == strQuery)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " -El argumento 'strQuery' no puede ser NULL.</error>");
            if (m_log.isDebugEnabled())
                m_log.debug("<listando>" + enlaceTransaccional + " - " + strQuery + "</listando>");
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            List<H> lista;
            Query query = s.createQuery(strQuery);
            for (Map.Entry<String, String> param : params.entrySet())
            {
                query.setParameter(param.getKey(), param.getValue());
            }
            lista = query.list();
            Hibernate.initialize(lista);
            //if ( m_log.isDebugEnabled () ) m_log.debug ( lista );//<-Forzamos la inicializacion.
            return lista;
        }
        catch (HibernateException e)
        {
            final String error = "<error><msg>" + enlaceTransaccional + " - No se pudier&oacute;n listar los BaseObject seg&uacute;n el Query</msg><parametros><query>" + strQuery + "</query><valores>" + params + "</valores></parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Devuelve un listado para un query que utiliza un agregador para
     * obtener un conjunto de subobjetos de persistencia.
     * The supported aggregate functions are
     * </p><div class="itemizedlist"><ul compact="compact" type="disc"><li><p>
     * <tt class="literal">avg(...), sum(...), min(...), max(...)</tt>
     * </p></li><li><p>
     * <tt class="literal">count(*)</tt>
     * </p></li><li><p>
     * <tt class="literal">count(...), count(distinct ...), count(all...)</tt>
     * </p></li></ul></div><p>
     * The <tt class="literal">distinct</tt> and <tt class="literal">all</tt> keywords may be used and have
     * the same semantics as in SQL.
     * </p><pre class="programlisting">select distinct cat.name from eg.Cat cat
     * select count(distinct cat.name), count(cat) from eg.Cat cat</pre>
     * <link> http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html</link>
     * <link> http://www.devarticles.com/c/a/Java/Hibernate-HQL-in-Depth/1/</link>
     *
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     * @param strQuery            sentencia de HQL necesaria para saber que traer de la base de datos.
     *
     * @return List de {@link Object} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public List queryListOfAggregateFunction(final String strQuery, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        return queryListOfAggregateFunction(strQuery, Collections.EMPTY_MAP, enlaceTransaccional);
    }

    /**
     * Devuelve un listado para un query que utiliza un agregador para
     * obtener un conjunto de subobjetos de persistencia.
     * The supported aggregate functions are
     * </p><div class="itemizedlist"><ul compact="compact" type="disc"><li><p>
     * <tt class="literal">avg(...), sum(...), min(...), max(...)</tt>
     * </p></li><li><p>
     * <tt class="literal">count(*)</tt>
     * </p></li><li><p>
     * <tt class="literal">count(...), count(distinct ...), count(all...)</tt>
     * </p></li></ul></div><p>
     * The <tt class="literal">distinct</tt> and <tt class="literal">all</tt> keywords may be used and have
     * the same semantics as in SQL.
     * </p><pre class="programlisting">select distinct cat.name from eg.Cat cat
     * select count(distinct cat.name), count(cat) from eg.Cat cat</pre>
     * <link> http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html</link>
     * <link> http://www.devarticles.com/c/a/Java/Hibernate-HQL-in-Depth/1/</link>
     *
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     * @param strQuery            sentencia de HQL necesaria para saber que traer de la base de datos.
     * @param params              par&aacute;metros para alimentar el query.
     *
     * @return List de {@link Object} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public List queryListOfAggregateFunction(final String strQuery, final Map<String, String> params, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (null == strQuery)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " -El argumento 'strQuery' no puede ser NULL.</error>");
            if (m_log.isDebugEnabled())
                m_log.debug("<listandoDerivados>" + enlaceTransaccional + " - " + strQuery + "</listandoDerivados>");
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            List<H> lista;
            Query query = s.createQuery(strQuery);
            for (Map.Entry<String, String> param : params.entrySet())
            {
                query.setParameter(param.getKey(), param.getValue());
            }
            lista = query.list();
            Hibernate.initialize(lista);
            return lista;
        }
        catch (HibernateException e)
        {
            final String error = "<error><msg>" + enlaceTransaccional + " - No se pudier&oacute;n listar los derivados seg&uacute;n el Query</msg><parametros><query>" + strQuery + "</query><valores>" + params + "</valores></parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }

    /**
     * Devuelve un listado de los {@link H} que se encuentran en persistencia.
     *
     * @param strQuery            a realizar.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     *
     * @return List de {@link H} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public H uniqueResult(String strQuery, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        return uniqueResult(strQuery, Collections.EMPTY_MAP, enlaceTransaccional);
    }

    /**
     * Devuelve un listado de los {@link H} que se encuentran en persistencia.
     *
     * @param strQuery            a realizar.
     * @param enlaceTransaccional enlace para hacer at&oacute;micas las transacciones.
     * @param params              par&aacute;metros para alimentar el query.
     *
     * @return List de {@link H} en caso de encontrarlo, una lista vacia en caso contrario.
     *
     * @throws com.psiqueware.alheli.dao.DAOException si se presenta alg&uacute;n problema.
     * @noinspection unchecked
     */
    public H uniqueResult(final String strQuery, final Map<String, String> params, final EnlaceTransaccional enlaceTransaccional) throws DAOException
    {
        try
        {
            if (null == strQuery)
                throw new NullArgumentException("<error>" + enlaceTransaccional + " -El argumento 'strQuery' no puede ser NULL.</error>");
            if (m_log.isDebugEnabled())
                m_log.debug("<listando>" + enlaceTransaccional + " - " + strQuery + "</listando>");
            Session s = (Session) enlaceTransaccional.continuaTransaccion();
            H salida;
            Query query = s.createQuery(strQuery);

            for (Map.Entry<String, String> param : params.entrySet())
            {
                query.setParameter(param.getKey(), param.getValue());
            }
            salida = (H) query.uniqueResult();
            Hibernate.initialize(salida);
            if (m_log.isDebugEnabled()) m_log.debug("<msg>" + enlaceTransaccional + " produjo: " + salida + "</msg>");
            return salida;
        }
        catch (HibernateException e)
        {
            final String error = "<error><msg>" + enlaceTransaccional + " - No se pudier&oacute;n listar los BaseObject seg&uacute;n el Query</msg><parametros><query>" + strQuery + "</query><valores>" + params + "</valores></parametros></error>";
            m_log.error(error);
            m_log.error(e);
            enlaceTransaccional.rollBack();
            throw new DAOException(error, e);
        }
    }


}

